package mchecca.model;

import java.util.ArrayList;
import java.util.Comparator;

/**
 * Class for a DecisionLinesEvent.
 * <p>
 * This is the main model for a {@link DecisionLinesEvent}
 * The three main components are the following
 * <ol>
 * 	<li>Number of choices (lines) the event will hold</li>
 * 	<li>Number of rounds to participate in</li>
 * 	<li>List of lines that hold the choice and edges</li>
 * </ol>
 * @author Michael Checca
 */
public class DecisionLinesEvent {
	/** Minimum amount of pixels between two edges on the same line */
	static int MIN_LINE_DIFF = 7;
	/** Number of rounds for this DLE */
	int numRounds;
	/** Number of choices for this DLE */
	int numChoices;
	/** List of {@link Line}s for this DLE */
	ArrayList<Line> lines;

	/**
	 * Creates a new DecisionLinesEvent.
	 * <p>
	 * The {@link ArrayList} of lines is created and stored.
	 * @param numChoices Number of choices
	 * @param numRounds Number of rounds
	 */
	public DecisionLinesEvent(int numChoices, int numRounds) {
		this.numRounds = numRounds;
		this.numChoices = numChoices;
		this.lines = new ArrayList<Line>();
		for (int i = 0; i < numChoices; i++) {
			lines.add(i, new Line(i));
		}
	}

	/**
	 * Gets the prioritized list of choices.
	 * <p>
	 * The algorithm is actually quite simple
	 * <ol>
	 * 	<li>Create a mathematical set of edges</li>
	 * 	<li>Sort them by pixel height</li>
	 * 	<li>For every edge, swap the positions of the left and right choices</li>
	 * <ol>
	 * @return Prioritized list of choices
	 */
	public String[] getPrioritizedChoices() {
		String[] choices = new String[lines.size()];
		for (int i = 0; i < lines.size(); i++)
			choices[i] = lines.get(i).choice;
		ArrayList<Edge> edges = new ArrayList<Edge>();
		for (Line l : lines)
			for (Edge e : l.getEdges())
				if (!edges.contains(e))
					edges.add(e);
		/* Sort edges */
		java.util.Collections.sort(edges, new Comparator<Edge>() {
			@Override
			public int compare(Edge e1, Edge e2) {
				if (e1.height > e2.height)
					return 1;
				else if (e1.height < e2.height)
					return -1;
				else
					return 0;
			}
		});
		for (Edge e : edges) {
			String temp = choices[e.left];
			choices[e.left] = choices[e.right];
			choices[e.right] = temp;
		}
		return choices;
	}

	/**
	 * Determines if the given edge is valid.
	 * <p>
	 * A valid edge is one the supports the following contraints
	 * <ol>
	 * 	<li>Neighboring Lines - edges can only be added between neighboring vertical lines</li>
	 * 	<li>Non-adjacency - edges cannot be added directly left or right of an existing edge within 7 pixels</li>
	 * </ol>
	 * @param e {@link Edge} to determine validity of
	 * @return True if edge is valid, false otherwise
	 */
	public boolean isValidEdge(Edge e) {
		if (e.left < 0 || e.left > numChoices ||
				e.right < 0 || e.left > numChoices ||
				(Math.abs(e.left - e.right) != 1))
			return false;
		ArrayList<Edge> associatedEdges = new ArrayList<Edge>();
		associatedEdges.addAll(lines.get(e.left).edges);
		associatedEdges.addAll(lines.get(e.right).edges);
		for (Edge edge : associatedEdges) {
			if (edge.left == e.left || edge.left == e.right ||
					edge.right == e.left || edge.right == e.right)
				if (Math.abs(e.height - edge.height) < MIN_LINE_DIFF)
					return false;
		}
		return e.height >= 0;
	}

	/**
	 * Adds the given edge to the event.
	 * @param e {@link Edge} to add
	 * @return True if edge was successfully added, false otherwise
	 */
	public boolean addEdge(Edge e) {
		boolean edgeLeft = false, edgeRight = false;
		if (isValidEdge(e)) {
			for (Line l : lines) {
				if (l.position == e.left) {
					l.edges.add(e);
					edgeLeft = true;
				} else if (l.position == e.right) {
					l.edges.add(e);
					edgeRight = true;
				}
			}
			return edgeLeft && edgeRight;
		}
		return false;
	}

	/**
	 * Sets the choice for the specified line number.
	 * @param position Number of the line
	 * @param choice Choice string to set
	 * @return True if choice was set, false otherwise
	 */
	public boolean setChoice(int position, String choice) {
		if (validChoice(position, choice)) {
			Line l = getLine(position);
			l.choice = choice;
			return true;
		} else
			return false;
	}

	public boolean validChoice(int position, String choice) {
		Line l = getLine(position);
		if (l == null || position < 0 || position >= numChoices || choice.length() == 0)
			return false;
		for (Line line : lines)
			if (line.choice.equals(choice))
				return false;
		return true;
	}

	/**
	 * Gets the list of {@link Line}s.
	 * @return List of lines
	 */
	public ArrayList<Line> getLines() {
		return lines;
	}

	/**
	 * Gets the number of choices.
	 * @return Number of choices
	 */
	public int getNumChoices() {
		return numChoices;
	}

	/**
	 * Gets the number of rounds.
	 * @return Number of rounds
	 */
	public int getNumRounds() {
		return numRounds;
	}

	/**
	 * Gets the choice of the specified line.
	 * @param position Position of line to get choice for
	 * @return Choice string for line at specified position
	 */
	public String getChoice(int position) {
		Line l = getLine(position);
		if (l == null)
			return "";
		return l.choice;
	}

	/**
	 * Gets the specified line.
	 * @param position Position to get line for
	 * @return {@link Line} of specified position
	 */
	public Line getLine(int position) {
		for (Line l : lines)
			if (l.position == position)
				return l;
		return null;
	}

	/** Determines if all choices are valid and the round is ready to begin.
	 * <p>
	 * All choices are valid if
	 * <ol>
	 * 	<li>Each individual choice is not an empty string</li>
	 * 	<li>Each individual choice is unique</li>
	 * </ol>
	 */
	public boolean allChoicesValid() {
		ArrayList<String> choices = new ArrayList<String>();
		int currentChoices = 0;
		for (int i = 0; i < lines.size(); i++) {
			if (lines.get(i).choice.length() > 0 && !choices.contains(lines.get(i).choice)){
				choices.add(lines.get(i).choice);
				currentChoices++;
			}
		}
		return currentChoices == numChoices;
	}

	/**
	 * Determines if two {@link DecisionLinesEvent}s are equal
	 * <p>
	 * Two {@link DecisionLinesEvent}s are equal if the have the same
	 * <ul>
	 * 	<li>Number of rounds</li>
	 * 	<li>Number of choices</li>
	 * 	<li>@{link Line}s</li>
	 * </ul>
	 * @param obj Other object to compare to this {@link DecisionLinesEvent}
	 * @return True if obj is the same is this object, false otherwise
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof DecisionLinesEvent) {
			DecisionLinesEvent other = (DecisionLinesEvent) obj;
			return numRounds == other.numRounds &&
					numChoices == other.numChoices &&
					lines.equals(other.lines);
		}
		return false;
	}

	/**
	 * Formats this {@link DecisionLinesEvent} nicely into a string.
	 * @return String representation of this {@link DecisionLinesEvent}
	 */
	@Override
	public String toString() {
		return String.format("DecisionLinesEvent [numRounds:%d, numChoices:%d]", numRounds, numChoices);
	}
}
